/*
 * Unidata Platform Community Edition
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 * This file is part of the Unidata Platform Community Edition software.
 *
 * Unidata Platform Community Edition is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Unidata Platform Community Edition is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

package org.unidata.mdm.data.service.segments.records.get;

import java.util.Date;
import java.util.Objects;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.unidata.mdm.core.type.timeline.Timeline;
import org.unidata.mdm.data.context.GetRecordTimelineRequestContext;
import org.unidata.mdm.data.context.GetRequestContext;
import org.unidata.mdm.data.context.RecordIdentityContextSupport;
import org.unidata.mdm.data.dto.GetRecordDTO;
import org.unidata.mdm.data.exception.DataExceptionIds;
import org.unidata.mdm.data.exception.DataProcessingException;
import org.unidata.mdm.data.module.DataModule;
import org.unidata.mdm.data.service.impl.CommonRecordsComponent;
import org.unidata.mdm.data.type.data.OriginRecord;
import org.unidata.mdm.data.type.keys.RecordKeys;
import org.unidata.mdm.system.type.pipeline.Start;

/**
 * @author Mikhail Mikhailov
 * Simple context validity checker and key finder.
 */
@Component(RecordGetStartExecutor.SEGMENT_ID)
public class RecordGetStartExecutor extends Start<GetRequestContext, GetRecordDTO> implements RecordIdentityContextSupport {
    /**
     * Common component.
     */
    @Autowired
    private CommonRecordsComponent commonRecordsComponent;
    /**
     * This logger.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(RecordGetStartExecutor.class);
    /**
     * This segment ID.
     */
    public static final String SEGMENT_ID = DataModule.MODULE_ID + "[RECORD_GET_START]";
    /**
     * Localized message code.
     */
    public static final String SEGMENT_DESCRIPTION = DataModule.MODULE_ID + ".record.get.start.description";
    /**
     * Constructor.
     */
    public RecordGetStartExecutor() {
        super(SEGMENT_ID, SEGMENT_DESCRIPTION, GetRequestContext.class, GetRecordDTO.class);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void start(GetRequestContext ctx) {
        setup(ctx);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String subject(GetRequestContext ctx) {
        setup(ctx);
        RecordKeys keys = ctx.keys();
        return keys.getEntityName();
    }

    protected void setup(GetRequestContext ctx) {

        if (ctx.setUp()) {
            return;
        }

        // 1. Already processed by .subject
        if (Objects.nonNull(ctx.keys()) && Objects.nonNull(ctx.currentTimeline())) {
            return;
        }

        // 2. Keys already supplied, but the TL is still to be loaded.
        if (Objects.nonNull(ctx.keys()) && Objects.isNull(ctx.currentTimeline())) {
            setupTimeline(ctx);
        // 3. Otherwise check input and run timeline with supplied identity
        } else  {

            if (!ctx.isValidRecordKey()) {
                final String message = "Ivalid input. Request context is not capable for record identification. {}";
                LOGGER.warn(message, ctx);
                throw new DataProcessingException(message, DataExceptionIds.EX_DATA_GET_INVALID_INPUT, ctx);
            }

            // 4. Identify and load interval
            setupTimeline(ctx);
        }

        ctx.setUp(true);
    }

    protected void setupTimeline(GetRequestContext ctx) {

        // Load interval view, either draft or regular.
        Timeline<OriginRecord> timeline = commonRecordsComponent.loadTimeline(
                GetRecordTimelineRequestContext.builder(ctx)
                    .fetchData(true)
                    .draftId(ctx.getDraftId())
                    .parentDraftId(ctx.getParentDraftId())
                    .forDate(Objects.isNull(ctx.getForDate()) ? new Date(System.currentTimeMillis()) : ctx.getForDate())
                    .build());

        RecordKeys keys = timeline.getKeys();
        if (keys == null) {
            final String message = "Record not found by supplied keys etalon id: [{}], origin id [{}], external id [{}], source system [{}], name [{}]";
            LOGGER.warn(message, ctx.getEtalonKey(), ctx.getOriginKey(), ctx.getExternalId(), ctx.getSourceSystem(), ctx.getEntityName());
            throw new DataProcessingException(message, DataExceptionIds.EX_DATA_GET_NOT_FOUND_BY_SUPPLIED_KEYS,
                    ctx.getEtalonKey(), ctx.getOriginKey(), ctx.getExternalId(), ctx.getSourceSystem(), ctx.getEntityName());
        }

        // Additional DIT check
        if (ctx.getEntityName() != null &&  keys.getEntityName() != null && !ctx.getEntityName().equals(keys.getEntityName())) {
            throw new DataProcessingException("Etalon id found '{}', but supplied entity name '{}' and key's entity name '{}' do not match.",
                    DataExceptionIds.EX_ENTITY_NAME_AND_ETALON_ID_MISMATCH,
                    ctx.getEtalonKey(), ctx.getEntityName(), keys.getEntityName());
        }

        ctx.currentTimeline(timeline);
        ctx.keys(keys);
    }
}
